package com.kayoxu.ble_printer

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Handler
import android.os.Message
import android.view.View
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import cn.shomes.smart_lock_flutter.utils.ble.FlutterData
import cn.shomes.smart_lock_flutter.utils.ble.FlutterData.BleDevice
import com.google.gson.reflect.TypeToken
import com.kayoxu.ble_printer.utils.*
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_CONNECTED
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_CONNECTE_FAILED
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_CONNECTE_NODEVICE
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_DISABLE
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_DISCONNECTED
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_ENABLE
import com.kayoxu.ble_printer.utils.EventType.Companion.BLE_FOUND
import com.szsicod.print.escpos.PrinterAPI
import com.szsicod.print.io.BluetoothAPI
import com.szsicod.print.log.AndroidLogCatStrategy
import com.szsicod.print.log.Logger
import com.szsicod.print.log.Utils
import io.flutter.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.*
import io.flutter.plugin.common.EventChannel.EventSink
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.*
import java.util.*
import kotlin.collections.ArrayList


/** BlePrinterPlugin */

inline fun <reified T> getGsonType() = object : TypeToken<T>() {}.type

open class BlePrinterPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler, PluginRegistry.ActivityResultListener {

    private lateinit var channel: MethodChannel
    var activity: Activity? = null

    private var eventChannel: EventChannel? = null
    var eventChannelHome: EventChannel? = null
    var eventChannelBle: EventChannel? = null
    var basicMessageChannel: BasicMessageChannel<Any>? = null

    private var mPrinter: PrinterAPI? = null
    private var dataUtils: DataUtils? = null
    private var bluetoothAdapter: BluetoothAdapter? = null

    private var mBluetoothApi: BluetoothAPI? = null


    private var bluetoothDevices = ArrayList<FlutterData.BleDevice>()

    var flutterResult: Result? = null

    var intentType: String? = null
    var intentMac: String? = null
    var intentPrintData: String? = null
    var printOneByOne = true


    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.kayoxu.plugin.ble_printer")
        channel.setMethodCallHandler(this)
        registerWithEvent(flutterPluginBinding.binaryMessenger)
        registerWithEventHome(flutterPluginBinding.binaryMessenger)
        registerWithEventBle(flutterPluginBinding.binaryMessenger)
        eventSinks.clear()
        eventSinksHome.clear()
        eventSinksBle.clear()
    }


    private fun registerWithEvent(messenger: BinaryMessenger?) {
        eventSinks.clear()
        eventChannel = EventChannel(messenger, "com.kayoxu.plugin/bleEvent")
        eventChannel!!.setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(arguments: Any?, events: EventSink?) {
                if (null != events) eventSinks.add(events)
            }

            override fun onCancel(arguments: Any?) {

            }
        })
    }

    private fun registerWithEventHome(messenger: BinaryMessenger?) {
        eventSinksHome.clear()
        eventChannelHome = EventChannel(messenger, "com.kayoxu.plugin/bleEventHome")
        eventChannelHome!!.setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(arguments: Any?, events: EventSink?) {
                if (null != events) eventSinksHome.add(events)
            }

            override fun onCancel(arguments: Any?) {

            }
        })

    }

    private fun registerWithEventBle(messenger: BinaryMessenger?) {
        eventSinksBle.clear()
        eventChannelBle = EventChannel(messenger, "com.kayoxu.plugin/bleEventBle")
        eventChannelBle!!.setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(arguments: Any?, events: EventSink?) {
                if (null != events) eventSinksBle.add(events)
            }

            override fun onCancel(arguments: Any?) {

            }
        })

    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
        Log.d("onActivityResult", "onActivityResult")
        when (requestCode) {
            ENABLE_BT -> {
                when (resultCode) {
                    Activity.RESULT_OK -> {
                        FlutterDataUtils.sendEvent(BLE_ENABLE, "");
                    }
                    Activity.RESULT_CANCELED -> {
                        FlutterDataUtils.sendEvent(BLE_DISABLE, "");
                    }
                }

                when (intentType) {
                    "bleInit" -> {
                        when (resultCode) {
                            Activity.RESULT_OK -> {
                                bleConnectByMac(intentMac ?: "")
                            }
                            Activity.RESULT_CANCELED -> {
                                flutterResult?.success(-2)
                                flutterResult = null
                            }
                        }
                    }
                    "searchBle" -> {
                        when (resultCode) {
                            Activity.RESULT_OK -> {
                                bleSearch()
                            }
                            Activity.RESULT_CANCELED -> {

                            }
                        }
                    }
                    "connectBle" -> {
                        when (resultCode) {
                            Activity.RESULT_OK -> {
                                bleConnectByMac(intentMac ?: "")
                            }
                            Activity.RESULT_CANCELED -> {

                            }
                        }
                    }
                    "printData" -> {
                        when (resultCode) {
                            Activity.RESULT_OK -> {
                                _printData()
                            }
                            Activity.RESULT_CANCELED -> {

                            }
                        }
                    }


                }
            }
            else -> {
            }
        }
        return false
    }


    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        activity = binding.activity

        Utils.init(activity)
        // 增加日志文件，true开启日志输出
        PrinterAPI.getInstance().setOutput(true)
        // 添加终端日志
        Logger.addLogStrategy(AndroidLogCatStrategy())
        mPrinter = PrinterAPI.getInstance()
        dataUtils = DataUtils.getInstance(activity)
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()


        val filter = IntentFilter()
        filter.addAction(BluetoothDevice.ACTION_FOUND)
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
        activity?.registerReceiver(mReceiver, filter)
        binding?.addActivityResultListener(this)
        SharedUtils.share().init(activity)

        mBluetoothApi = BluetoothAPI(activity)


        Utils.init(activity)
        if (true) {
            PrinterAPI.getInstance().setOutput(true)
            Logger.addLogStrategy(AndroidLogCatStrategy())
        }


    }


    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {

        intentType = call.method

        when (call.method) {
            "bleInit" -> {

                PermissionsUtils.request(activity, object : PermissionsUtils.PermissionsCallback {
                    override fun onAccept() {

                        val bleDevice = SharedUtils.share().bleDevice

                        if (mPrinter?.isConnect == true) {
                            flutterResult = null
                            result.success(BLE_CONNECTED)
                        } else {
                            if (null == bleDevice || bleDevice.mac?.isEmpty() == true) {
                                result.success(-1)
                            } else {
                                bleConnectByMac(bleDevice.mac!!)
                                flutterResult = result
                            }
                        }
                    }

                    override fun onRefused() {
                        result.success(0)
                    }
                }, Manifest.permission.BLUETOOTH,
                        Manifest.permission.BLUETOOTH_ADMIN,
                        Manifest.permission.ACCESS_COARSE_LOCATION,
                        Manifest.permission.ACCESS_FINE_LOCATION
                )

            }
            "bleMac" -> {
                val bleDevice = SharedUtils.share().bleDevice
                result.success(bleDevice?.mac ?: "")
            }
            "isBleConnected" -> {
                result.success(mPrinter?.isConnect ?: false)
            }
            "searchBle" -> {
                result.success("")

                PermissionsUtils.request(activity, object : PermissionsUtils.PermissionsCallback {
                    override fun onAccept() {
                        bleSearch()
                    }

                    override fun onRefused() {
                    }
                }, Manifest.permission.BLUETOOTH,
                        Manifest.permission.BLUETOOTH_ADMIN,
                        Manifest.permission.ACCESS_COARSE_LOCATION,
                        Manifest.permission.ACCESS_FINE_LOCATION
                )

            }
            "stopSearchBle" -> {
                result.success("")
                if (bluetoothAdapter?.isDiscovering == true) {
                    bluetoothAdapter?.cancelDiscovery()
                }

            }

            "connectBle" -> {

                PermissionsUtils.request(activity, object : PermissionsUtils.PermissionsCallback {
                    override fun onAccept() {
                        val argument = call.argument<String>("data")
                        val bleDevice = GsonUtils.gsonToBean<BleDevice>(argument, BleDevice::class.java)

                        SharedUtils.share().saveBleDevice(bleDevice)

                        bleConnectByMac(bleDevice?.mac
                                ?: "", (call.argument<Boolean?>("autoOpenBle") ?: true))
                    }

                    override fun onRefused() {
                    }
                }, Manifest.permission.BLUETOOTH,
                        Manifest.permission.BLUETOOTH_ADMIN,
                        Manifest.permission.ACCESS_COARSE_LOCATION,
                        Manifest.permission.ACCESS_FINE_LOCATION
                )
                result.success("")
            }
            "disconnectBle" -> {
                result.success("")
                if (mPrinter?.isConnect == true) {
                    mPrinter?.disconnect()
                }
            }


            "printData" -> {
                result.success("")

                val data = call.argument<String>("data")
                intentPrintData = data
                _printData()
            }

            "printTitle" -> {
                result.success("")
                val data = call.argument<String?>("data") ?: ""

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printTitle(data)
                        }
                        l.join()
                    }
                } else {
                    _printTitle(data)
                }
            }

            "printText" -> {
                result.success("")
                val left = call.argument<String?>("left") ?: ""
                val right = call.argument<String?>("right") ?: ""
                val bold = call.argument<Boolean?>("bold") ?: false
                val doubleWidth = call.argument<Boolean?>("doubleWidth") ?: false
                val doubleHeight = call.argument<Boolean?>("doubleHeight") ?: false
                val underLine = call.argument<Boolean?>("underLine") ?: false

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printText(left, right, bold, doubleWidth, doubleHeight, underLine)
                        }
                        l.join()
                    }
                } else {
                    _printText(left, right, bold, doubleWidth, doubleHeight, underLine)
                }
            }
            "printQrcode" -> {
                result.success("")
                val data = call.argument<String?>("data") ?: ""
                val height = call.argument<Int?>("height") ?: 384

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printQrcode(data, height)
                        }
                        l.join()
                    }
                } else {
                    _printQrcode(data, height)
                }
            }
            "printBarcode" -> {
                result.success("")
                val data = call.argument<String?>("data") ?: ""
                val height = call.argument<Int?>("height") ?: 162
                val width = call.argument<Int?>("width") ?: 2
                val showCode = call.argument<Boolean?>("showCode") ?: false

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printBarcode(data, width, height, showCode)
                        }
                        l.join()
                    }
                } else {
                    _printBarcode(data, width, height, showCode)

                }
            }
            "printDashLine" -> {
                result.success("")

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printDashLine()
                        }
                        l.join()
                    }
                } else {
                    _printDashLine()
                }
            }

            "printEmptyLine" -> {
                val lines = call.argument<Int?>("lines") ?: 1
                result.success("")

                if (printOneByOne) {
                    GlobalScope.launch(Dispatchers.Default) {
                        val l = GlobalScope.launch {
                            _printEmptyLine(lines)
                        }
                        l.join()
                    }
                } else {
                    _printEmptyLine(lines)
                }
            }

            else -> {
                result.notImplemented()
            }
        }
    }

    private fun _printData() {

        if (!bleOpened()) {
            return
        }
        GlobalScope.launch {
            __printData()
        }
    }

    private fun __printData() {
        val data = intentPrintData

        val list = GsonUtils.gsonToBean<List<FlutterData.PrinterTicketData>>(data, getGsonType<List<FlutterData.PrinterTicketData>>())

        for (d in list) {
            _printTitle(d.company ?: "");
            _printEmptyLine();

            _printText("起点")
            _printDashLine();
            _printText(
                    d.addressStart ?: "",
                    "", bold = true, doubleWidth = true, doubleHeight = true
            );
            _printEmptyLine();

            _printText("车牌号", right = "驾驶员");
            _printDashLine();
            _printText(
                    d.carNum ?: "",
                    right = d.driverName ?: "",
                    bold = true,
                    doubleWidth = true,
                    doubleHeight = true);
            _printEmptyLine();

            _printText("开单时间")
            _printDashLine();
            _printText(d.ticketTime ?: "", bold = true);
            _printEmptyLine();

            _printText("开单人");
            _printDashLine();
            _printText(d.ticketUser ?: "", bold = true);
            _printEmptyLine();

            _printText("终点");
            _printDashLine();
            _printText(d.addressEnd ?: "", bold = true);
            _printEmptyLine();

            _printText("运单号");
            _printDashLine();
            _printText(d.ticketId ?: "", bold = true);
            _printEmptyLine();

            _printText("当日趟次");
            _printDashLine();
            _printText(d.ticketTimes ?: "", bold = true);
            _printEmptyLine(1)

            _printBarcode(d.ticketBarCode ?: "",
                    height = 90, width = 1);
            _printEmptyLine();
            _printQrcode(d.ticketQrCode ?: "");
            _printEmptyLine(2);
            _printDashLine();
        }
    }


    private fun _printEmptyLine(lines: Int = 1) {
        var data = ""
        if (lines > 0) {
            for (index in 0 until lines) {
                data += "\n"
            }
        }
        mPrinter?.init()
        mPrinter?.printString(data, "utf8", true);
    }

    private fun _printDashLine() {
//        PrintUtils.printLine(mPrinter)
        mPrinter!!.init()
        mPrinter!!.printString("--------------------------------", "utf8", true)
    }

    private fun _printBarcode(data: String, width: Int = 2, height: Int = 162, showCode: Boolean = false) {
        mPrinter!!.init()
//        PrintUtils.printBarcode(mPrinter, data, width, height, showCode)
        mPrinter!!.setAlignMode(1)
//        mPrinter?.setBarCodeHeight(height)
//        mPrinter?.setBarCodeWidth(width)
//        mPrinter?.setBarCodeHeight(height)
//        mPrinter?.setBarCodeWidth(width)
        mPrinter!!.printBarCode(73, data.length, data)
    }

    private fun _printQrcode(data: String, height: Int = 240) {
        mPrinter!!.init()
        mPrinter!!.setAlignMode(1)

        mPrinter?.printQRCode(data, 5, false)
        //        PrintUtils.printQrCode(mPrinter, data, height)
    }

    private fun _printText(left: String, right: String = "", bold: Boolean = false, doubleWidth: Boolean = false, doubleHeight: Boolean = false, underLine: Boolean = false) {
        mPrinter!!.init()
        mPrinter!!.setEmphasizedMode(if (bold) 1 else 0)
        mPrinter!!.setEnableUnderLine(if (underLine) 1 else 0)
        mPrinter!!.setCharSize(if (doubleHeight) 1 else 0, if (doubleWidth) 1 else 0)

        if (right == "") {
            mPrinter!!.printString(left, "gbk", true)
        } else {
            val data = PrintUtils.leftRight(left, right, doubleWidth)
            mPrinter!!.printString(data, "gbk", true)
        }
    }

    private fun _printTitle(data: String) {
        val infoContent: View? = activity?.layoutInflater?.inflate(
                R.layout.printer_title, null)
        val title = infoContent?.findViewById<TextView>(R.id.title)
        title?.text = data
        val bitMap = PrintUtils.view2Bitmap(infoContent, 144)
//        PrintUtils.printBitmap(mPrinter, bitMap)
        mPrinter?.printRasterBitmap(bitMap)
    }

    private fun bleSearch() {
        if (!bleOpened()) return
        if (bluetoothAdapter!!.isDiscovering) {
            bluetoothAdapter!!.cancelDiscovery()
        }
        bluetoothAdapter!!.startDiscovery()
    }


    //用于接受连接状态消息的 Handler
    private val mHandler: Handler = @SuppressLint("HandlerLeak")
    object : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                PrinterAPI.SUCCESS -> {
                    FlutterDataUtils.sendEvent(BLE_CONNECTED, "");
                    try {
                        flutterResult?.success(BLE_CONNECTED)
                        flutterResult = null
                    } catch (e: Exception) {
                    }
                }
                PrinterAPI.ERR_PARAM,
                PrinterAPI.FAIL -> {
                    FlutterDataUtils.sendEvent(BLE_CONNECTE_FAILED, "");
                    flutterResult?.success(BLE_CONNECTE_FAILED)
                    flutterResult = null
                }
                BLE_DISCONNECTED -> {
                    FlutterDataUtils.sendEvent(BLE_DISCONNECTED, "");
                    flutterResult?.success(BLE_DISCONNECTED)
                    flutterResult = null
                }
                BLE_CONNECTE_NODEVICE -> {
                    FlutterDataUtils.sendEvent(BLE_CONNECTE_NODEVICE, "");
                    flutterResult?.success(BLE_CONNECTE_NODEVICE)
                    flutterResult = null
                }
                999 -> {
//                    val devices = msg.obj as List<BluetoothDevice>
//                    val bluetoothDevice = devices[0]
                    bleConnectByMac("04:7F:0E:1D:21:CA")

                }
                else -> {
                }
            }
        }
    }

    private fun bleConnectByMac(mac: String, autoOpenBle: Boolean = true) {
        intentMac = mac;

        if (autoOpenBle) {
            if (!bleOpened()) return
        }
        GlobalScope.launch {
            if (mPrinter!!.isConnect) {
                if (mBluetoothApi?.address == mac) {
                    return@launch
                }
                mPrinter!!.disconnect()
                mHandler.obtainMessage(BLE_DISCONNECTED).sendToTarget()
            }
            if (mPrinter?.isConnect != true) {
                mPrinter?.io?.closeDevice();
            }

            if (null == mBluetoothApi) mBluetoothApi = BluetoothAPI(activity)
            mBluetoothApi?.setPin("0000") //我厂自动配对的;
            mBluetoothApi?.setPairStrings(arrayOf("58HB6")) //设置自动匹配的设备名
            mBluetoothApi?.checkDevice(mac)

            mHandler.obtainMessage(mPrinter!!.connect(mBluetoothApi)).sendToTarget()
        }
    }


    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
        eventChannel?.setStreamHandler(null)
        eventChannelHome?.setStreamHandler(null)
        eventChannelBle?.setStreamHandler(null)
    }

    override fun onDetachedFromActivityForConfigChanges() {
        onDetachedFromActivity()
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
        onAttachedToActivity(binding)
    }

    override fun onDetachedFromActivity() {
        onDetachedFromActivity()
        activity?.unregisterReceiver(mReceiver)
        if (mPrinter!!.isConnect) {
            mPrinter!!.disconnect()
        }
    }


    private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action
            if (BluetoothDevice.ACTION_FOUND == action) {
                val device = intent
                        .getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)

                if (null != device && null != device.name && "" != device.name) {
                    val uuids = device.uuids
                    val alias = device.alias
                    val bondState = device.bondState
                    val name = device.name
                    val type = device.type
                    val address = device.address
                    val majorDeviceClass = device.bluetoothClass.majorDeviceClass


                    if (type == 3 || majorDeviceClass == 1536 || null != uuids) {
                        for (i in bluetoothDevices) {
                            if (i.mac == address) return
                        }
                        bluetoothDevices.add(FlutterData.BleDevice(name, address, if (bondState == BOND_BONDED) 1 else 0))
                        FlutterDataUtils.sendEvent(BLE_FOUND, bluetoothDevices);
                    }
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
                    == action) {

            }
        }
    }

    private fun bleOpened(): Boolean {
/*         // 表示手机不支持蓝牙
        if (bluetoothAdapter == null) return
         // 判断本机蓝牙是否打开
        if (!bluetoothAdapter!!.isEnabled) bluetoothAdapter!!.enable()*/

        return if (null != bluetoothAdapter && bluetoothAdapter!!.isEnabled) {
            true
        } else {
            val enableIntent = Intent(
                    BluetoothAdapter.ACTION_REQUEST_ENABLE)
            activity?.startActivityForResult(enableIntent,
                    ENABLE_BT)
            false
        }
    }

    companion object {
        private val TAG = BlePrinterPlugin::class.java.simpleName
        var eventSinks: ArrayList<EventSink> = ArrayList()
        var eventSinksHome: ArrayList<EventSink> = ArrayList()
        var eventSinksBle: ArrayList<EventSink> = ArrayList()

        var EXTRA_DEVICE_ADDRESS = "device_address"
        var EXTRA_RE_PAIR = "re_pair"
        var EXTRA_DEVICE_NAME = "device_name"

        val CONNECT_DEVICE = 1 //选择设备

        val ENABLE_BT = 2 //启动蓝牙

        val REQUEST_SELECT_FILE = 3 //选择文件

        val REQUEST_PERMISSION = 4


        fun sendEvent(params: Any?) {
            var params = params
            Log.e(TAG, params.toString() + "____" + eventSinks.size)
            if (params != null && params !is String) params = GsonUtils.beanToJson(params)
            for (eventSink in eventSinks) {
                eventSink.success(params)
            }
            for (eventSink in eventSinksHome) {
                eventSink.success(params)
            }
            for (eventSink in eventSinksBle) {
                eventSink.success(params)
            }
        }
    }

}
